/*
 * Character.cpp
 *
 *  Created on: Aug 11, 2013
 *      Author: jason
 */

#include "../../include/Common.hpp"
#include "../../include/Objects/Characters.hpp"
#include "../../include/GameModes/Dialogue.hpp"
#include "../../include/GameModes/TownMode/TownMode.hpp"
#include <iostream>

namespace OForce_Object {

// Input Variables:
//   positionx - position x-coordinate
//   positiony - position y-coordinate
//   sizex     - size in the x direction
//   sizey     - size in the y direction
//   charSheet - path to spritesheet
//   _name     - name of sprite
//   _class    - sprite's character classnextDialogue(0),
//   _numAnim  - number of possible animations
//   ID        - unique character ID
Character::Character(float positionx, float positiony, float sizex, float sizey,
		std::string charSheet,
		std::string _name, std::string _class, int _numAnim, int ID) :
		  mName(_name),
		  mCharClass(_class),
		  numAnimations(_numAnim),
		  currentDialogue(0),
		  hasUnseenDialogue(true),
		  currentFrame(0),
		  currentAnim(0),
		  timer(0.0f),
		  animDuration(1.25f),
		  hasPortrait(false),
		  mCollide(true),
		  mActive(true),
		  mDuration(0.0f),
		  mRandomCooldown(0.0f),
		  mStateSaved(false),
		  mMoveSpeed(moveSpeed),
		  mSavedSpeed(moveSpeed),
		  controlledByEvent(NULL),
		  spriteSheet(charSheet),
		  customAnimationTimer(0.0f),
		  customInProgress(false)
{
	mId = ID;
	rect.setSize(sf::Vector2f((float)sizex, (float)sizey));
	rect.setPosition(sf::Vector2f(positionx, positiony));
	//		rect.setFillColor(sf::Color (30,30,30,30));
	if (!texture.loadFromFile(charSheet.c_str()))
		std::cout << "Error loading character's spritesheet\n";
	sprite.setPosition(sf::Vector2f(positionx, positiony));
	sprite.setTexture(texture);
	mSize = sf::Vector2i(sizex, sizey);
	mDir = DOWN;
	mSavedDirection = DOWN;
	objectType = CHARACTER_TYPE;
	stats = new Stats();
	// set base class (Object) values
	mVisible = true;
	mCollide = true;
	currentLayer = 0;
	controlType = STILL;
	prevType = STILL;
}

Character::~Character()
{
	delete stats;
	stats = NULL;
	for (std::vector<OForce::GenericItem *>::iterator it = vItem.begin();
				it != vItem.end(); it++) {
		if (*it != NULL) {
			delete *it;
			*it = NULL;
		}
	}
}

void Character::acquireEvent(OForce_Mode::CharacterEvent *event)
{
	if (event == NULL) {
		std::cerr << "Error: no event passed\n";
		return;
	}
	if (controlledByEvent != NULL) {
		std::cerr << "Error: character was already controlled.\n";
		return;
	}
	controlledByEvent = event;
	prevType = controlType;
	controlType = SCRIPTED;
}

void Character::releaseEvent(OForce_Mode::CharacterEvent *event)
{
	if (event == NULL) {
		std::cerr << "Error: attempted to release NULL event.\n";
		return;
	}
	if (controlledByEvent == NULL) {
		std::cerr << "Error: character is not scripted so there is no event";
		std::cerr << " to release.\n";
		return;
	} else if (controlledByEvent != event) {
		std::cerr << "Error: attempted to release character from an event";
		std::cerr << " that did not belong to it.\n";
	} else {
		controlledByEvent = NULL;
		controlType = prevType;
	}
}

void Character::saveState()
{
	mStateSaved = true;
	mSavedDirection = mDir;
	mSavedSpeed = mMoveSpeed;
	prevType = controlType;
}

void Character::resumeState()
{
	mStateSaved = false;
	mDir = mSavedDirection;
	mSavedSpeed = mMoveSpeed;
	controlType = prevType;
}

void Character::setDirection(DIRECTION dir)
{
	updateAnim(dir);
}

void Character::lookAt(float x, float y)
{
	float myX = sprite.getPosition().x;
	float myY = sprite.getPosition().y;
	// Don't bother looking at yourself
	if (myX == x && myY == y) {
		return;
	}
	// handle targets on the same axis
	if (fabs(x - myX) < 32.0) {
		if (y > myY) {
			setDirection(DOWN);
		} else {
			setDirection(UP);
		}
		return;
	}
	if (fabs(y - myY) < 32.0) {
		if (x > myX) {
			setDirection(RIGHT);
		} else {
			setDirection(LEFT);
		}
		return;
	}
	// off axis cases:
	if (x < myX) {
		// down-right & up-right
		if (y < myY) {
			// less than 45 degrees
			if ((y - myY) / (x - myX) < 1.0) {
				setDirection(LEFT);
			} else {
				setDirection(UP);
			}
		} else {
			if ((y - myY) / (x - myX) < 1.0) {
				setDirection(LEFT);
			} else {
				setDirection(DOWN);
			}
		}
	} else {
		if (y < myY) {
			if ((y - myY) / (x - myX) < 1.0) {
				setDirection(RIGHT);
			} else {
				setDirection(UP);
			}
		} else {
			if ((y - myY) / (x - myX) < 1.0) {
				setDirection(RIGHT);
			} else {
				setDirection(DOWN);
			}
		}
	}
}

void Character::randomDirection()
{
	float r = 4.0*(float)rand()/((float)RAND_MAX);
	if (r > 3.0) {
		setDirection(UP);
	} else if (r > 2.0) {
		setDirection(DOWN);
	} else if (r > 1.0) {
		setDirection(LEFT);
	} else {
		setDirection(RIGHT);
	}
	r = 1.5+(float)rand() / ((float)RAND_MAX)/4.5;
	mDuration = r;
	r = 0.2 + (float) rand() / ((float)RAND_MAX)/1.25;
	mRandomCooldown = r;
}

float Character::distanceMoved()
{
	return dt * mMoveSpeed;
}

// TODO Separate this from animation
void Character::moveToNextPosition()
{
	float dist = distanceMoved();
	if (mDir == UP) {
		if (sprite.getPosition().y > 0) {
			sprite.move(0, -dist);
			if (OForce_Mode::TownMode::getCurrentInstance()->getObjectManager()->
					collisionCheck(this, 0)) {
				sprite.move(0,dist);
			}
		}
	} else if (mDir == DOWN) {
		if (sprite.getPosition().y < OForce_Mode::TownMode::getCurrentInstance()->getWorldSize().y) {
			sprite.move(0, dist);
			if (OForce_Mode::TownMode::getCurrentInstance()->getObjectManager()->
					collisionCheck(this, 0)) {
				sprite.move(0,-dist);
			}
		}
	} else if (mDir == LEFT) {
		if (sprite.getPosition().x > 0) {
			sprite.move(-dist, 0);
			if (OForce_Mode::TownMode::getCurrentInstance()->getObjectManager()->
					collisionCheck(this, 0)) {
				sprite.move(dist, 0);
			}
		}
	} else if (mDir == RIGHT) {
		// Maybe better to just put collision boundaries around the world?
		if (sprite.getPosition().x < OForce_Mode::TownMode::getCurrentInstance()->getWorldSize().x) {
			sprite.move(dist, 0);
			if (OForce_Mode::TownMode::getCurrentInstance()->getObjectManager()->
					collisionCheck(this, 0)) {
				sprite.move(-dist, 0);
			}
		}
	}
}

// TODO separate this from movement
void Character::updateAnim(int anim)
{
	currentAnim = anim;
	if (anim == 0) {
		mDir = DOWN;
	} else if (anim == 1) {
		mDir = LEFT;
	} else if (anim == 2) {
		mDir = RIGHT;
	} else if (anim == 3) {
		mDir = UP;
	}
	sf::Vector2i source (currentFrame, currentAnim);
	// choose sprite frame
	sprite.setTextureRect(sf::IntRect(currentFrame * mSize.x, currentAnim * mSize.y, mSize.x, mSize.y));
//	sprite.setTextureRect(sf::IntRect(0,0,32,32));
}

void Character::addItem(OForce::GenericItem *item)
{
	if (vItem.size() <= 4) {
		vItem.push_back(item);
	}
}

void Character::addItem(int itemID)
{
	if (vItem.size() <= 4) {
		OForce::GenericItem *item = NULL;
		if (itemID >= 0) {
			item = new OForce::Item (itemID);
			vItem.push_back(item);
		}
	}
}

OForce::GenericItem *Character::getItem(int slot)
{
	if (slot < vItem.size()) {
		return vItem[slot];
	} else {
		return NULL;
	}
}

void Character::removeItem(int slot)
{
	if (slot < vItem.size()) {
		delete vItem[slot];
		vItem.erase(vItem.begin()+slot);
	}
}

/// Update
void Character::update() {
	if (customInProgress) {
		customAnimationTimer -= dt;
		if (customAnimationTimer < 0.0f) {
			customInProgress = false;
		}
	}
	if (!customInProgress) {
		sprite.setTexture(texture);
	}
	if (timer > (animDuration / vNumFrames[currentAnim])) {
		currentFrame++;
		timer = 0.0;
	}
	if (currentFrame >= vNumFrames[currentAnim]) {
		currentFrame = 0;
	}
	if (mDuration > 0.0) {
		if (controlType == RANDOM) {
			if (OForce_Mode::TownMode::getCurrentInstance()->getCurrentState()
					!= OForce_Mode::DIALOGUE) {
				mDuration -= dt;
				moveToNextPosition();
			}
		}
	} else {
		if (controlType == RANDOM) {
			if (mRandomCooldown > 0.0) {
				mRandomCooldown -= dt;
			} else {
				randomDirection();
			}
		} if (controlType == SCRIPTED) {
			if (OForce_Mode::TownMode::getCurrentInstance()->getCurrentState()
					!= OForce_Mode::DIALOGUE) {
				moveToNextPosition();
			}
		}
	}
	timer += dt;
	sf::Vector2i source (currentFrame, currentAnim);
	// choose sprite frame
	sprite.setTextureRect(sf::IntRect(currentFrame * mSize.x, currentAnim * mSize.y, mSize.x, mSize.y));
}

void Character::setNumAnimations(std::vector<int> _vAnim) {
	std::vector<int>::iterator it = _vAnim.begin();
	for (; it != _vAnim.end(); it++) {
		vNumFrames.push_back(*it);
	}
}

void Character::addAnimFrames(int numFrames) {
	vNumFrames.push_back(numFrames);
}

void Character::draw() {
	if (mVisible) {
		Window.draw(sprite);
	}
}

// Dialogue
void Character::addDialogueByID(int id) {
	vDialogue.push_back(id);
}

void Character::initiateDialogue() {
	// TODO switch this to StateManager rather than TownMode once battle mode is implemented
	if (vDialogue.size() == 0) {
		std::cerr << "No dialogues instantiated for this character\n";
		return;
	}
	OForce_Mode::TownMode::getCurrentInstance()->getDialogueManager()->
			startDialogue(vDialogue[currentDialogue]);
	incrementDialogue();
}

void Character::incrementDialogue() {
	int prevDialogue = currentDialogue;
	currentDialogue++;
	if (currentDialogue >= vDialogue.size()) {
		currentDialogue = 0;
		hasUnseenDialogue = false;
	}
}

void Character::setNextDialogue(int next) {
	if (next >= vDialogue.size()) {
		std::cerr << "Error: tried to set dialogue to invalid dialogue.\n";
		return;
	}
	currentDialogue = next;
}

void Character::setPortrait(std::string filename) {
	portraitFile = filename;
	hasPortrait = true;
}

void Character::setCustomAnimation(std::string file, float t)
{
	customAnimation = file;
	customAnimationTimer = t;
	customInProgress = true;
	customTexture.loadFromFile(file);
}

} // namespace OForce_Object
